#pragma once

#include <stack>
#include <memory>

#include "Path/Path.h"
#include "Framework/ApplicationSubsystem.h"
#include "Debug/Breakpoint.h"

#include "Containers/Delegate.h"

#include "ScriptSubsystem.m.h"

class CScriptNamespace;
class CByteCodeReader;
class CByteCodeFile;
class CGSharpClass;
class CScriptInterpreter;

class CRuntimeScriptFile;
class CFunctionInterpreter;

class CDebugServer;


RCLASS()
class WHENGINE_API RScriptSubsystem :public RApplicationSubsystem
{
    ROBJECT

public:
    static RScriptSubsystem* Get();

    virtual void Initialize()  override ;
    virtual void Deinitialize() override ;

    void LoadByteCode(const TVector<uint8_t>& InByteCode,const CString& ScriptLibraryName );

    void LoadScript(std::shared_ptr<CByteCodeFile> InByteCodeFile,const CString& ScriptLibraryName);

    CAny InvokeStaticFunction(const CString & InClassFullName, const CString & InFunctionName, const TVector<CAny> & InArgs);
    
    void StartDebugServer(uint16_t InPort );

    void StopDebugServer();

    bool IsDebugServerStarted() const;

    bool IsDebuggerAttached() const;

    void MarkAutoExitAppWhenDebuggerDetached() const;

    void SetScriptFiles(const TVector<std::shared_ptr<CRuntimeScriptFile>> & InScriptFiles);

    std::shared_ptr<CRuntimeScriptFile> GetLoadedFile(const SPath & InFilePath);

    TVector<std::shared_ptr<CRuntimeScriptFile>> GetLoadedFiles() const;

    //@InLine is 0-based
    //allow invoke in any thread , except load script file at same time in other thread
    void SetBreakpoint(const SPath & InFilePath, uint32_t InLine, CBreakpointSlot::EBreakpointState NewState );

    //get all set break points, include enabled and disabled
    //allow invoke in any thread , except load script file at same time in other thread
    TVector<std::shared_ptr<CBreakpointSlot>> GetAllBreakpoints() const;

    TVector<std::shared_ptr<CFunctionInterpreter>> & GetScriptCallStack();

    void WaitBreakpointOver();


    TDelegate<void> OnHitBreakpoint;

    //allow invoke in any thread
    void Continue();

    //allow invoke in any thread
    bool IsHitingBreakpoint() const;

protected:

    TVector<std::shared_ptr<CRuntimeScriptFile>> AllScriptFiles;

    TVector<std::shared_ptr<CBreakpointSlot>> EnabledBreakpoints;

    TVector<std::shared_ptr<CFunctionInterpreter>> ScriptCallStack;

    friend class CScriptFunctionBody;

    CDebugServer * DebugServer = nullptr;

    enum class EState
    {
        Running,
        HitingBreakpoint,
    };

    std::atomic<EState> InterpreterState = EState::Running;

};
